﻿<!--
> Muaz Khan     - https://github.com/muaz-khan 
> MIT License   - https://www.webrtc-experiment.com/licence/
> Experiments   - https://github.com/muaz-khan/WebRTC-Experiment
-->
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>WebRTC PeerConnection » Tutorial | Muaz Khan</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
        <link rel="author" type="text/html" href="https://plus.google.com/+MuazKhan">
        <meta name="author" content="Muaz Khan">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <link rel="stylesheet" href="https://cdn.webrtc-experiment.com/style.css">        
        <style>
            p {
                padding: .8em;
                padding-bottom: 0;
            }

            li {
                border-bottom: 1px solid rgb(189, 189, 189);
                border-left: 1px solid rgb(189, 189, 189);
                padding: .5em;
            }

            .highlight { color: rgb(0, 8, 189); }
        </style>
        <script>
            document.createElement('article');
            document.createElement('footer');
        </script>
		
        <script type="text/javascript" src="https://cdn.webrtc-experiment.com/syntax/sh_main.min.js"> </script>
        <script type="text/javascript" src="https://cdn.webrtc-experiment.com/syntax/sh_javascript.min.js"> </script>
        <script type="text/javascript" src="https://cdn.webrtc-experiment.com/syntax/sh_html.min.js"> </script>
        <link type="text/css" rel="stylesheet" href="https://cdn.webrtc-experiment.com/syntax/sh_style.css">
    </head>

    <body onload="sh_highlightDocument();">
        <article>
            <header style="text-align: center;">
                <h1>
                    <a href="https://www.webrtc-experiment.com/">WebRTC</a> PeerConnection » Tutorial
                </h1>            
                <p>
                    <a href="https://www.webrtc-experiment.com/">HOME</a>
                    <span> &copy; </span>
                    <a href="http://www.MuazKhan.com/" target="_blank">Muaz Khan</a>
                    
                    .
                    <a href="http://twitter.com/WebRTCWeb" target="_blank" title="Twitter profile for WebRTC Experiments">@WebRTCWeb</a>
                    
                    .
                    <a href="https://github.com/muaz-khan?tab=repositories" target="_blank" title="Github Profile">Github</a>
                    
                    .
                    <a href="https://github.com/muaz-khan/WebRTC-Experiment/issues?state=open" target="_blank">Latest issues</a>
                    
                    .
                    <a href="https://github.com/muaz-khan/WebRTC-Experiment/commits/master" target="_blank">What's New?</a>
                </p>
            </header>

            <div class="github-stargazers"></div>

            <blockquote style="background: #f3b7b7;border: 5px solid black;border-radius: 5px;">
                This tutorial is <span style="border: 1px dotted red; background: yellow; padding: 2px 5px;">out-dated (written in 2013)</span>. Please try a LIVE demo here instead: <a href="https://webrtc.github.io/samples/src/content/peerconnection/pc1/">https://webrtc.github.io/samples/src/content/peerconnection/pc1/</a>
                <br><br>
                In case if above link gets broken: <a href="https://github.com/webrtc/samples">https://github.com/webrtc/samples</a>

                <br><br>
                Please check this tutorial (seems written by Sam Dutton): <a href="https://codelabs.developers.google.com/codelabs/webrtc-web/#0">https://codelabs.developers.google.com/codelabs/webrtc-web/#0</a>
            </blockquote>
            
            <section class="experiment">
                <h2 class="header">Explains how to</h2>
                <ol>
                    <li>use WebRTC peer connection API</li>
                    <li>write one-to-one video sharing application</li>
                    <li>use socket.io or websockets for signaling</li>
                </ol>
            </section>
			
            <section class="experiment">
                <h2 class="header">Suggestions</h2>
                <ol>
                    <li>
                        If you're newcomer, newbie or beginner; you're suggested to try <a href="https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RTCMultiConnection" target="_blank">RTCMultiConnection.js</a> or <a href="https://github.com/muaz-khan/WebRTC-Experiment/tree/master/DataChannel" target="_blank">DataChannel.js</a> libraries.
                    </li>
					
                    <li>
                        Another recommended tutorial is: <a href="https://www.webrtc-experiment.com/docs/how-to-use-rtcpeerconnection-js-v1.1.html" target="_blank">How to use RTCPeerConnection.js? (v1.5)</a>
                    </li>
                </ol>
		
            </section>
            
            <section class="experiment">
                <h2 class="header">WebRTC PeerConnection API</h2>
                <ol>
                    <li>getUserMedia API to attach local media stream (webcam/microphone)</li>
                    <li>Offer/Answer model to establish connection between two users</li>
                    <li>ICE Server (STUN/TURN) to pass firewalls and NATs</li>
                    <li>Signaling server to share offer/answer messages; or ice candidates among users</li>
                </ol>
                <p>
                    An PeerConnection object can be initialized like this:
                </p>
                <pre class="sh_javascript">
var connection = new [webkit|moz]RTCPeerConnection(
    'ice-servers', 
    'optional-arguments'
);
</pre>
                <ol>
                    <li>You can suggest one ore more ICE servers using 1st parameter. It is an array of ICE servers.</li>
                    <li>You can suggest for stuff like "open data connection" or "prefer DTLS/SRTP" using 2nd parameter</li>
                </ol>

                <p>
                    Here is a simple example to create offer: 
                </p>
                
                <pre class="sh_javascript">
var connection = new [webkit|moz]RTCPeerConnection(
    'ice-servers', 
    'optional-arguments'
);

connection.createOffer(getOfferSDP, onfailure, <a href="#sdpConstraints">sdpConstraints</a>);
function getOfferSDP(offerSDP) {
    connection.setLocalDescription(offerSDP [, successCallback, failureCallback]);
    
    console.log('offer sdp', offerSDP.sdp);
    console.log('type',      offerSDP.type);
};
</pre>

                <p>
                    Here is a simple example to create answer: 
                </p>
                
                <pre class="sh_javascript">
var connection = new [webkit|moz]RTCPeerConnection(
    'ice-servers', 
    'optional-arguments'
);

// "setRemoteDescription" is quickly called for answerer
var remoteSessionDescription = new RTCSessionDescription(offerSDP);
connection.setRemoteDescription(remoteSessionDescription, successCallback, failureCallback);

connection.createAnswer(getAnswerSDP, onfailure, <a href="#sdpConstraints">sdpConstraints</a>);
function getAnswerSDP(answerSDP) {
    connection.setLocalDescription(answerSDP);
    
    console.log('answer sdp', answerSDP.sdp);
    console.log('type',       answerSDP.type);
};
</pre>

                <p>
                    You created both offer and answer; now complete the handshake:
                </p>
                
                <pre class="sh_javascript">
// "setRemoteDescription" is called "later" for offerer
// it will complete the offer/answer handshake

var remoteSessionDescription = new RTCSessionDescription(answerSDP);
connection.setRemoteDescription(remoteSessionDescription, successCallback, failureCallback);
</pre>
            </section>
            
            <section class="experiment">
                <h2 class="header">A working example with WebSockets</h2>
                <ol>
                    <li>A websocket connection will be opened for both users</li>
                    <li>1st user will create offer; and share with 2nd user via websocket connection</li>
                    <li>2nd user will create answer; and share with 1st user via websocket connection</li>
                    <li>Both users will share ICE candidates too; with each other; accordingly</li>
                </ol>
                <p>
                    So, 1st step is to open websocket connection:
                </p>
                <pre class="sh_javascript">
var socket       = new WebSocket('ws://domain.com:80/');
socket.onopen    = function() {};
socket.onmessage = function() {};
</pre>
                <p>
                    Assuming that there is a "Start Peer Connection" button; when offerer will click it; we will create offer on his side; and share with other user:
                </p>
                
                <pre class="sh_javascript">
&lt;button id="start-peer-connection"&gt;Start Peer Connection&lt;/button&gt;

// javascript code
var button = document.getElementById('start-peer-connection');
button.onclick = function() {
    this.disabled = true;

    var peer = new [webkit|moz]RTCPeerConnection(iceServers, optional);    
    peer.createOffer(function(offerSDP) {
        peer.setLocalDescription(offerSDP);
        socket.send({
            targetUser: 'target-user-id',
            offerSDP: offerSDP
        });
    }, onfailure, <a href="#sdpConstraints">sdpConstraints</a>);
};
</pre>

                <p>
                    Defining "iceServers" and "optional" variables:
                </p>
                
                <pre class="sh_javascript">
var STUN = {
    urls: 'stun:stun.l.google.com:19302'
};

var TURN = {
    urls: 'turn:turn.bistri.com:80',
    credential: 'homeo',
    username: 'homeo'
};

var iceServers = {
   iceServers: [STUN, TURN]
};

// DTLS/SRTP is preferred on chrome
// to interop with Firefox
// which supports them by default

var DtlsSrtpKeyAgreement = {
   DtlsSrtpKeyAgreement: true
};

var optional = {
   optional: [DtlsSrtpKeyAgreement]
};
</pre>

                <p>
                    Here is a list of events and methods can be used with RTCPeerConnection object:
                </p>
                
                <ol>
                    <li>onicecandidate</li>
                    <li>onaddstream</li>
                    <li>oniceconnectionstatechange</li>
                    <li>onsignalingstatechange</li>
                    <li>onremovestream</li>
                    <li>addStream</li>
                    <li>removeStream</li>
                    <li>close</li>
                    <li>+ many others</li>
                </ol>
                
                <p>
                    Each method and event has its own importance; however, for the sake of simplicity, we will try only "onicecandidate" and "onaddstream":
                </p>
                
                <ol>
                    <li>PeerConnection on chrome performs ICE trickling process to track list of all available ICE candidates for current user; a browsers usually make UDP requests to ICE server (i.e. STUN or TURN) to gather ICE candidates will be used to traverse the NAT of current user. "onicecandidate" event is fired for each trickled ICE candidate.</li>
                    <li>"onaddstream" is fired to return remote media stream attached by the other user.</li>
                </ol>
                
                <p>
                    ICE trackling is not necessary; even it is not part of WebRTC specification. Alternatives will be explained later in this document.
                </p>
                
                <pre class="sh_javascript">
peer.onaddstream = function(mediaStream) {
    video.src = webkitURL.createObjectURL(mediaStream);
};

peer.onicecandidate = function(event) {
    var candidate = event.candidate;
    if(candidate) {
        socket.send({
            targetUser: 'target-user-id',
            candidate: candidate
        });
    }
};
</pre>

                <p>
                    A media-stream can be attached using "addStream" method:
                </p>
                
                <pre class="sh_javascript">
// it is suggested to use gumadapter.js instead:
// <a href="https://github.com/muaz-khan/gumadapter">https://github.com/muaz-khan/gumadapter</a>

navigator.webkitGetUserMedia(MediaConstraints, OnMediaSuccess, OnMediaError);
var MediaConstraints = {
    audio: true,
    video: true
};

function OnMediaError(error) {
    console.error(error);
}

function OnMediaSuccess(mediaStream) {
    peer.addStream(mediaStream);
}
</pre>

                <p>
                    Remeber, chrome now supports attachment of multiple media streams:
                </p>
                
                <pre class="sh_javascript">
peer.addStream(audioStream);
peer.addStream(videoStream);
peer.addStream(screenCapturingStream);
</pre>

                <p>
                    It will fire "onaddstream" multiple times according to number of media streams attached. Duplicate streams attachment is not allowed.
                </p>
                
                <p>
                    Putting above all together:
                </p>
                
                <pre class="sh_javascript">
&lt;button id="start-peer-connection"&gt;Start Peer Connection&lt;/button&gt;

// javascript code
var button = document.getElementById('start-peer-connection');
button.onclick = function() {
    this.disabled = true;
    
    // it is suggested to use gumadapter.js instead:
    // <a href="https://github.com/muaz-khan/gumadapter">https://github.com/muaz-khan/gumadapter</a>
    navigator.webkitGetUserMedia(MediaConstraints, OnMediaSuccess, OnMediaError);
    var MediaConstraints = {
        audio: true,
        video: true
    };

    function OnMediaError(error) {
        console.error(error);
    }

    function OnMediaSuccess(mediaStream) {
        var peer = new [webkit|moz]RTCPeerConnection(iceServers, optional);
        
        peer.addStream(mediaStream);
        
        peer.onaddstream = function(mediaStream) {
            video.src = webkitURL.createObjectURL(mediaStream);
        };

        peer.onicecandidate = function(event) {
            var candidate = event.candidate;
            if(candidate) {
                socket.send({
                    targetUser: 'target-user-id',
                    candidate: candidate
                });
            }
        };
        
        peer.createOffer(function(offerSDP) {
            peer.setLocalDescription(offerSDP, successCallback, failureCallback);
            socket.send({
                targetUser: 'target-user-id',
                offerSDP: offerSDP
            });
        }, onfailure, <a href="#sdpConstraints">sdpConstraints</a>);
    }
};

var STUN = {
    urls: 'stun:stun.l.google.com:19302'
};

var TURN = {
    urls: 'turn:turn.bistri.com:80',
    credential: 'homeo',
    username: 'homeo'
};

var iceServers = {
   iceServers: [STUN, TURN]
};

// DTLS/SRTP is preferred on chrome
// to interop with Firefox
// which supports them by default

var DtlsSrtpKeyAgreement = {
   DtlsSrtpKeyAgreement: true
};

var optional = {
   optional: [DtlsSrtpKeyAgreement]
};
</pre>

                <p>
                    OK. Offer is completed. Now, it is time to create answer.
                </p>
				
                <ol>
                    <li>Create answer only when you've offer</li>
                    <li>"setRemoteDescription" should be called earlier before creating answer</li>
                </ol>
				
                <pre class="sh_javascript">
socket.onmessage =  function(e) {
    var data = e.data;
    if(data.targetUser !== self && data.offerSDP) {
        createAnswer(offerSDP);
    }
};
function createAnswer(offerSDP) {
    // it is suggested to use gumadapter.js instead:
    // <a href="https://github.com/muaz-khan/gumadapter">https://github.com/muaz-khan/gumadapter</a>
    navigator.webkitGetUserMedia(MediaConstraints, OnMediaSuccess, OnMediaError);
    var MediaConstraints = {
        audio: true,
        video: true
    };

    function OnMediaError(error) {
        console.error(error);
    }

    function OnMediaSuccess(mediaStream) {
        var peer = new [webkit|moz]RTCPeerConnection(iceServers, optional);
        
        peer.addStream(mediaStream);
        
        peer.onaddstream = function(mediaStream) {
            video.src = webkitURL.createObjectURL(mediaStream);
        };

        peer.onicecandidate = function(event) {
            var candidate = event.candidate;
            if(candidate) {
                socket.send({
                    targetUser: 'target-user-id',
                    candidate: candidate
                });
            }
        };
        
        // remote-descriptions should be set earlier
        // using offer-sdp provided by the offerer
        var remoteDescription = new RTCSessionDescription(offerSDP);
        peer.setRemoteDescription(remoteDescription, successCallback, failureCallback);
		
        peer.createAnswer(function(answerSDP) {
            peer.setLocalDescription(answerSDP, successCallback, failureCallback);
            socket.send({
                targetUser: 'target-user-id',
                answerSDP: answerSDP
            });
        }, onfailure, <a href="#sdpConstraints">sdpConstraints</a>);
    }
};
</pre>

                <p>
                    Answer is also completed. Now, second last step is to set remote descriptions for offerer; same as we did for answerer:
                </p>
				
                <pre class="sh_javascript">
socket.onmessage =  function(e) {
    var data = e.data;
    if(data.targetUser !== self && data.answerSDP) {
        // completing the handshake; this code is for offerer
        var remoteDescription = new RTCSessionDescription(answerSDP);
        peer.setRemoteDescription(remoteDescription, successCallback, failureCallback);
    }
};
</pre>

                <p>
                    Now, last step is to "add ICE candidate" for each peer. It works like this:
                </p>
				
                <ol>
                    <li>Offerer gathers ICE candidates using ICE trickling process</li>
                    <li>Offerer sends those candidates to answerer</li>
                    <li>Answerer adds those candidates using "addIceCandidate" method</li>
                    <li>Answerer also gathers its own ICE candidates using same ICE trickling process</li>
                    <li>Answerer also sends his candidates to offerer</li>
                    <li>Offerer also adds those candidates using "addIceCandidate" method</li>
                </ol>
				
                <p>
                    Again, ICE trickling is not "officially" included in WebRTC specification; so, it is chrome-only feature. Firefox merges all ice candidates in session descriptions. You can merge candidates in offerer/answer sdp on chrome too; see next section.
                </p>
				
                <pre class="sh_javascript">
socket.onmessage =  function(e) {
    var data = e.data;
    if(data.targetUser !== self && data.candidate) {
        var candidate     = data.candidate.candidate;
        var sdpMLineIndex = data.candidate.sdpMLineIndex;
		
        peer.addIceCandidate(new [moz]RTCIceCandidate({
            sdpMLineIndex: sdpMLineIndex,
            candidate    : candidate
        }), successCallback, failureCallback);
    }
};
</pre>
				
            </section>
			
            <section class="experiment">
                <h2 class="header">How to merge ice candidates?</h2>
                <ol>
                    <li>Detect "null" candidate in "onicecandidate" event</li>
                    <li>Detect "iceGatheringState == 'complete'" in "ongatheringchange"</li>
                </ol>
				
                <pre class="sh_javascript">
peer.onicecandidate =  function(e) {
    var candidate = e.candidate;
    // typeof candidate == 'undefined'
    // !candidate -or- !!candidate == false
	
    if(typeof candidate == 'undefined') {
        send_SDP();
    }
};

peer.ongatheringchange =  function(e) {
    if (e.currentTarget &&
        e.currentTarget.iceGatheringState === 'complete') {
        send_SDP();
    }
};

function send_SDP() {
    socket.send({
        targetUser: 'target-user-id',
        sdp       :  peer.localDescription
    });
}
</pre>
            </section>
			
            <section class="experiment">
                <h2 class="header">How to use socket.io for signaling?</h2>
				
                <pre class="sh_javascript">
var socket = io.connect('http://domain.com:80/');
socket.on('message', function(data) {
    if(data.offerSDP)  {}
    if(data.answerSDP) {}
    if(data.candidate) {}
});
</pre>

                <p>
                    You can use either "send" or "emit" method to send the data. However, it is recommended to override "send" method like this:
                </p>
				
                <pre class="sh_javascript">
socket.send = function(data) {
    socket.emit('message', data);
});

// Now you can send "any-kind" of data like this:
socket.send('string');
socket.send([array]);
socket.send({
    targetUser: 'target-user-id',
    sdp       : 'offerSDP || answerSDP'
});
</pre>
            </section>
			
            <section class="experiment">
                <h2 class="header" id="sdpConstraints">sdpConstraints?</h2>
                <pre class="sh_javascript">
// for Chrome:
var sdpConstraints = {
    optional: [],
    mandatory: {
        OfferToReceiveAudio: true,
        OfferToReceiveVideo: true
    }
};

// for Firefox:
var sdpConstraints = {
    OfferToReceiveAudio: true,
    OfferToReceiveVideo: true
};
</pre>
            </section>
			
            <section class="experiment">
                <h2 class="header" id="demos">Demos? Examples?</h2>
                <ol>
                    <li><a href="https://www.webrtc-experiment.com/socket.io/">https://www.webrtc-experiment.com/socket.io/</a></li>
                    <li><a href="https://www.webrtc-experiment.com/websocket/">https://www.webrtc-experiment.com/websocket/</a></li>
                </ol>
            </section>
			
            <section class="experiment">
                <h2 class="header">Simplest Demo using socket.io</h2>
                <pre class="sh_javascript">
// http://cdn.webrtc-experiment.com/socket.io/PeerConnection.js

var offerer = new <strong title="Constructor takes three arguments.
1) websocket url
2) socket event and 
3) custom user-id">PeerConnection</strong>('<strong class="highlight" title="websocket url">http://domain:port</strong>', '<strong class="highlight" title="socket event to emit/receive messages">message</strong>', '<strong class="highlight" title="custom user-id">offerer</strong>');
offerer.<strong title="local or remote stream">onStreamAdded</strong> = function(e) {
   document.body.appendChild(e.mediaElement);
};
</pre>

                <pre class="sh_javascript">
var answerer = new <strong title="Constructor takes three arguments.
1) websocket url
2) socket event and
3) custom user-id">PeerConnection</strong>('<strong class="highlight" title="websocket url">http://domain:port</strong>', '<strong class="highlight" title="socket event to emit/receive messages">message</strong>', '<strong class="highlight" title="custom user-id">answerer</strong>');
answerer.<strong title="local or remote stream">onStreamAdded</strong> = function(e) {
   document.body.appendChild(e.mediaElement);
};
answerer.<strong title="Ask other user to establish peer connection with you!">sendParticipationRequest</strong>(<strong class="highlight" title="user-id of the offerer">'offerer'</strong>);
</pre>
            </section>
        
            <section class="experiment own-widgets latest-commits">
                <h2 class="header" id="updates" style="color: red; padding-bottom: .1em;"><a href="https://github.com/muaz-khan/WebRTC-Experiment/commits/master" target="_blank">Latest Updates</a></h2>
                <div id="github-commits"></div>
            </section>
        
            <section class="experiment">
                <h2 class="header" id="feedback">Feedback</h2>
                <div>
                    <textarea id="message" style="border: 1px solid rgb(189, 189, 189); height: 8em; margin: .2em; outline: none; resize: vertical; width: 98%;" placeholder="Have any message? Suggestions or something went wrong?"></textarea>
                </div>
                <button id="send-message" style="font-size: 1em;">Send Message</button><small style="margin-left: 1em;">Enter your email too; if you want "direct" reply!</small>
            </section>
            
        </article>
        
        <a href="https://github.com/muaz-khan/WebRTC-Experiment" class="fork-left"></a>
        
        <footer>
            <p>
                <a href="https://www.webrtc-experiment.com/">WebRTC Experiments</a>
                © <a href="https://plus.google.com/+MuazKhan" rel="author" target="_blank">Muaz Khan</a>
                <a href="mailto:muazkh@gmail.com" target="_blank">muazkh@gmail.com</a>
            </p>
        </footer>
    
        <!-- commits.js is useless for you! -->
        <script src="https://cdn.webrtc-experiment.com/commits.js" async> </script>
    </body>
</html>
